#include <stdbool.h>    //bool
#include <stdint.h>     //uint32_t, uint64_t...
#include <string.h>     //memcpy(3)
#include <stdio.h>      //printf(3) 
#include <stdlib.h>     //exit(3)
#include <aio.h>        //aiocb struct
#include <errno.h>	  //errno defines
#include <unistd.h>     //fork(3), chdir(3), sysconf(3)
#include <signal.h>     //signal(3)
#include <sys/types.h>  //mkfifo(3)
#include <sys/stat.h>   //umask(3)
#include <syslog.h>     //syslog(3), openlog(3), closelog(3)
#include <fcntl.h>	  //open(3)

#define MAX_NAME_LEN  		64
#define MAX_DATA_LEN  		256
#define BUF_SIZE	  		0x400
#define FIFOS_FILDES_MAX	  4

enum parse_states{
	INVALID,
	STRING,
	INT,
	FLOAT
};

typedef struct var{
	char name[MAX_NAME_LEN];
	size_t data_size;
	char* data;
	struct var* next;
	struct var* prev;
}var;

int gSignal_num = 0;
bool gDaemon_is_open = false;
bool gDaemon_should_close = false;

void display_help(){
	const char* help = "Usage: vastore [OPTION...] [VARIABLES]...\n"
					   "Brief: Stores variables in a subprocess\n"
				       "\n"
				       "Options:\n"
				       "-h, --help         display this help\n"
				       "-i, --init         init process\n"
				       "-a, --add          add variables\n"
				       "-r, --remove       remove variables\n"
				       "-l, --list         list variables\n"
				       "-p, --print        print variable\n"
				       "  -pi              force integer output\n"			  
				       "  -pf              force float output\n"
				       "-c                 turn variable into constant\n"
				       "-G, --generate     generate private session key\n"
				       "-P, --private      make vairable private\n"
				       "-S, --session      enter with private session key\n"
				       "-D, --directory    create a directory variable\n"
				       "\n"
				       "example:\n"
				       "$ vastore -i var=\"my variable\" age=8\n"
				       "Inits vastore with var and age set.\n"
				       "\n"
				       "Report bugs to https://notabug.org/srburns/vastore/issues";
	puts(help);
};

int check_arg(char* arg){
	if(arg[0] >= '0' && arg[0] <= '9')return INVALID;
	
	char* p = arg;
	int period_count = 0;
	
	for(char c = 0; *p != '=' && *p != '\0'; p++){
		c = *p;
		if(c == '_') continue;
		if(c >= '0' && c <= '9') continue;
		c |= 0x20;	// ASCII case insensitive mask
		if(c >= 'a' && c <= 'z') continue;
		break;
	}
	
	if(*p == '='){
		p++;
		bool is_number = true;
		char* cp = p; // compare the first char afeter '='
		for(; *p; p++){
			if(*p < '0' || *p > '9'){
				if(p == cp && *p == '-') continue;
				if(*p == '.'){
					period_count++;
					continue;
				}
				is_number = false;
				break;
			}
		}
		if(is_number){
			if(period_count == 0){
				return INT;
			} 
			else if (period_count == 1){
				return FLOAT;
			}
		}
		return STRING;
	}
	return INVALID;
	
}

void open_daemon(char* name, char* path, char* outfile, char* errfile, char* infile){
	if(!name)name = "vastored";
	if(!path)path = "/";
	if(!outfile)outfile = "/dev/null";
	if(!errfile)errfile = "/dev/null";
	if(!infile)infile = "/dev/null";
	
	int child;
	
	if((child = fork()) < 0){
		fprintf(stderr, "[VASTORE] Error: failed fork\n");
		exit(EXIT_FAILURE);
	}
	if(child > 0){
		exit(EXIT_SUCCESS);
	}
	if(setsid() < 0){
		fprintf(stderr, "[VASTORE] Error: failed setsid\n");
		exit(EXIT_FAILURE);
	}
	
	signal(SIGCHLD, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	
	if((child = fork()) < 0){
		fprintf(stderr, "[VASTORE] Error: failed 2nd fork\n");
		exit(EXIT_FAILURE);
	}
	if(child > 0){
		exit(EXIT_SUCCESS);
	}
	
	umask(0);
	chdir(path);
	
	int fd;
	for(fd = sysconf(_SC_OPEN_MAX); fd >= 0; fd--){
		close(fd);
	}
	
	stdin = fopen(infile, "r");
	stdout = fopen(outfile, "w+");
	stderr = fopen(errfile, "w+");
	
	openlog(name, LOG_PID, LOG_DAEMON);
	
	gDaemon_is_open = true;
}

void close_daemon(){
	closelog();
	gDaemon_is_open = false;
}

struct aiocb* async_read(struct aiocb* aiobuf, int fd, char* buf, int size){
	memset(aiobuf, 0, sizeof(struct aiocb));
	
	aiobuf->aio_buf = buf;
	aiobuf->aio_fildes = fd;
	aiobuf->aio_nbytes = size;
	aiobuf->aio_offset = 0;
	
	if(aio_read(aiobuf) < 0)return NULL;
	
	return aiobuf;
}

struct aiocb* async_write(struct aiocb* aiobuf, int fd, char* buf, int size){
	memset(aiobuf, 0, sizeof(struct aiocb));
	
	aiobuf->aio_buf = buf;
	aiobuf->aio_fildes = fd;
	aiobuf->aio_nbytes = size;
	aiobuf->aio_offset = 0;
	
	if(aio_write(aiobuf) < 0)return NULL;
	
	return aiobuf;
}

void daemon_closing_handler(int num){
	gSignal_num = num;
	gDaemon_should_close = true;
}

void run_test_daemon(){
	open_daemon(NULL, "/tmp", NULL, NULL, NULL);
	
	signal(SIGINT, daemon_closing_handler);
	signal(SIGHUP, daemon_closing_handler);
	signal(SIGTERM, daemon_closing_handler);
	
	char* fnin = "/tmp/vain.fifo";
	char* fnout = "/tmp/vaout.fifo";
	
	struct aiocb aio_read_buf = {0};
	struct aiocb aio_write_buf = {0};
	struct aiocb* aio_read_req;
	struct aiocb* aio_write_req;
	
	char read_buf[BUF_SIZE] = {0};
	char write_buf[BUF_SIZE] = {0};
	
	int finerr = 0, fouterr = 0;
	
	if(access(fnin, R_OK)){
		finerr = mkfifo(fnin, S_IRWXU);
	}
	
	if(access(fnout, W_OK)){
		fouterr = mkfifo(fnout, S_IRWXU);
	}
	
	if(finerr != 0 && finerr != EEXIST){
		syslog(LOG_NOTICE, "Failed to create input FIFO, err %d.", finerr);
		close_daemon();
		return;
	}
	
	if(fouterr != 0 && fouterr != EEXIST){
		syslog(LOG_NOTICE, "Failed to create output FIFO,  err %d.", fouterr);
		close_daemon();
		return;
	}
	
	bool fifos_failed = false;
	int fildes[FIFOS_FILDES_MAX] = {0};
	
	enum fildes_index{
		in_read,
		in_write,
		out_read,
		out_write
	};
	
	fildes[in_read] = open(fnin, O_RDONLY | O_NONBLOCK);
	fildes[in_write] = open(fnin, O_WRONLY | O_NONBLOCK);	// not used
	fildes[out_read] = open(fnout, O_RDONLY | O_NONBLOCK);   // not used
	fildes[out_write] = open(fnout, O_WRONLY | O_NONBLOCK);
	
	for(int i = 0; i < FIFOS_FILDES_MAX; i++){
		if(fildes[i] < 0){
			fifos_failed = true;
		}
	}
	
	if(fifos_failed){
		for(int i = 0; i < FIFOS_FILDES_MAX; i++){
			if(fildes[i] > 0){
				close(fildes[i]);
			}
		}
		syslog(LOG_NOTICE, "Failed to open FIFOs.");
		close_daemon();
		return;
	}

	syslog(LOG_NOTICE, "Vastore daemon initialized successfully.");

	while(gDaemon_should_close == false){
		int aio_read_err = 0, aio_write_err = 0;
		
		//READ
		aio_read_req = async_read(&aio_read_buf, fildes[in_read], read_buf, BUF_SIZE);
		aio_read_err = aio_error(aio_read_req);
		
		if(aio_read_err != ECANCELED && aio_read_err != EINVAL){
			if(aio_read_err == ENOSYS){
				for(int i = 0; i < FIFOS_FILDES_MAX; i++)close(fildes[i]);
				syslog(LOG_NOTICE, "No async IO implemented, closing daemon.");
				close_daemon();
				exit(EXIT_FAILURE);
			}
		}
		
		if(aio_error(aio_read_req) == 0){
			// read_buf
		}
		
		//WRITE
		aio_write_req = async_write(&aio_write_buf, fildes[out_write], write_buf, BUF_SIZE);
		aio_write_err = aio_error(aio_write_req);
		
		if(aio_write_err != ECANCELED && aio_write_err != EINVAL){
			if(aio_write_err == ENOSYS){
				for(int i = 0; i < FIFOS_FILDES_MAX; i++)close(fildes[i]);
				syslog(LOG_NOTICE, "No async IO implemented, closing daemon.");
				close_daemon();
				exit(EXIT_FAILURE);
			}
		}
		
		if(aio_error(aio_write_req) == 0){
			// buf writen successfully
		}
	}
	for(int i = 0; i < FIFOS_FILDES_MAX; i++)close(fildes[i]);
	syslog(LOG_NOTICE, "Process exited on signal %d.", gSignal_num);
	close_daemon();
	exit(EXIT_SUCCESS);
}

/*
	TO-DO:
	[x] Handle SIGTERM and SIGHUP
	[ ] Save vars to disk when SIGTERM
	[ ] Implement memory sharing
	[ ] Implement hashtable
	[ ] Implement private variables
	[ ] Implement filesystem (directories)	
*/

int main(int argc, char* argv[]){

	char c = 0;

	if(argc <= 1){
		display_help();
		exit(1);
	}
	
	if(argv[1][0] != '-'){
		// maybe it can print a list of variables by default
		display_help();
		exit(1);
	}
	
	if(strlen(argv[1]) == 0) exit(1);
	
	c = argv[1][1];
	
	if(c == 'h'){
		display_help();
		exit(0);
	}
	
	switch(c){
		case 'i':
			run_test_daemon();
			break;
		case 'a':
			break;
		case 'r':
			break;
		case 'l':
			break;
		case 'p':
			break;
		case 'c':
			break;
		case 'G':
			break;
		case 'P':
			break;
		case 'S':
			break;
		default:
			display_help();
			break;
			
	}
	return 0;
}
